//------------------------------------------------------------------------------
//  <copyright file="NavBenchmarkClient.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//  </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.Robotics.Navigation.Benchmark
{
    using System;
    using System.Net.Sockets;
    using System.Runtime.InteropServices;

    /// <summary>
    /// Nav benchmark client.
    /// </summary>
    public class NavBenchmarkClient
    {
        /// <summary>
        /// DNS name of the remote host.
        /// </summary>
        private readonly string hostname;

        /// <summary>
        /// Port number of the remote host.
        /// </summary>
        private readonly int port;

        /// <summary>
        /// Unique run ID.
        /// </summary>
        private readonly long runId;

        /// <summary>
        /// Initializes a new instance of the NavBenchmarkClient class.
        /// </summary>
        /// <param name="hostname">DNS name of the remote host.</param>
        /// <param name="port">Port number of the remote host.</param>
        public NavBenchmarkClient(string hostname, int port)
        {
            this.hostname = hostname;
            this.port = port;
            this.runId = DateTime.Now.Ticks;
        }

        /// <summary>
        /// Send a landmark message to the server.
        /// </summary>
        /// <param name="landmarkId">the current landmark Id</param>
        /// <param name="waitForResponse">a flag indicating if call should block waiting for a response</param>
        /// <returns>the response if waitForResponse is true.  If waitForResponse is false, it return 1 for success and 0 for fail.</returns>
        public int SendMessage(int landmarkId, bool waitForResponse)
        {
            int response = 0;
            bool retrySend = true;
            while (retrySend)
            {
                try
                {
                    using (TcpClient client = new TcpClient())
                    {
                        client.Connect(this.hostname, this.port);
                        using (NetworkStream stream = client.GetStream())
                        {
                            Message message = new Message() { LandmarkId = (byte)landmarkId, RunId = this.runId };
                            stream.Write(ConvertToByteArray(message, Marshal.SizeOf(message)), 0, Marshal.SizeOf(message));
                            Console.WriteLine("Message sent to NavBenchmark server. Landmark {0}.", landmarkId);

                            // message was sent OK...  stop retrying.
                            retrySend = false;

                            // wait for the response if requested (don't care what it is)
                            if (waitForResponse)
                            {
                                Console.Write("Waiting for response from NavBenchmark server...", landmarkId);
                                response = stream.ReadByte();
                                Console.WriteLine("Response received from NavBenchmark server.");
                            }
                        }
                    }
                }
                catch (SocketException)
                {
                    Console.WriteLine("SendMessage failed: SocketException. {0}", retrySend ? "Retrying" : "Write succeeded, but read failed. Aborting.");
                }
                catch (System.IO.IOException)
                {
                    Console.WriteLine("SendMessage failed: IOException. {0}", retrySend ? "Retrying" : "Write succeeded, but read failed. Aborting.");
                }
            }

            if (waitForResponse)
            {
                return response;
            }

            return 1;
        }

        /// <summary>
        /// Converts an object to array of bytes
        /// </summary>
        /// <param name="obj">The input object</param>
        /// <param name="size">The size of the object</param>
        /// <returns>A byte array representing the object</returns>
        private static byte[] ConvertToByteArray(object obj, int size)
        {
            byte[] bytes = new byte[size];
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(obj, ptr, true);
            Marshal.Copy(ptr, bytes, 0, size);
            Marshal.FreeHGlobal(ptr);
            return bytes;
        }

        /// <summary>
        /// Structure for a TCP navigation signal message.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1900:ValueTypeFieldsShouldBePortable", MessageId = "RunId",
            Justification = "use of explicit layout is for communication over TCP/IP purposes, not for marshalling to unmanaged code purposes"),
            StructLayout(LayoutKind.Explicit)]
        private struct Message
        {
            /// <summary>
            /// A landmark ID 
            /// </summary>
            [FieldOffset(0)]
            public byte LandmarkId;

            /// <summary>
            /// A unique run identifier
            /// </summary>
            [FieldOffset(1)]
            public long RunId;
        }
    }
}
